Skip to content

Strengthen TRE API authentication and clean up codebase - #4989

Merged
marrobi merged 35 commits into
microsoft:mainfrom
marrobi:copilot/redesign-tre-api-auth
Jul 29, 2026
Merged

Strengthen TRE API authentication and clean up codebase#4989
marrobi merged 35 commits into
microsoft:mainfrom
marrobi:copilot/redesign-tre-api-auth

Conversation

@marrobi

@marrobi marrobi commented Jul 22, 2026

Copy link
Copy Markdown
Member

The TRE API auth layer was a single god-object (AzureADAuthorization) mixing JWT validation, JWKS key management, Graph API calls, and role checking, with an unused AccessService abstraction sitting on top.

New api_app/auth/ package

Introduces a clean, layered auth package:

  • models.pyAuthenticatedUser (frozen Pydantic; roles is an immutable tuple so roles cannot be reassigned or mutated in place), TRERole/WorkspaceAccessRole StrEnums, has_any_role()/is_tre_admin() helpers
  • exceptions.py — Typed exception hierarchy (TokenExpired, TokenSignatureInvalid, TokenInvalid, InsufficientPermissions, WorkspaceNotFound); no silent swallowing
  • token_validator.pyTokenValidator using PyJWKClient for automatic JWKS key management; validates issuer + audience + expiry + signature
  • registry.py@lru_cache singletons: get_core_validator() for the TRE core app, get_workspace_validator(client_id) per workspace, all sharing a single PyJWKClient
  • dependencies.pyrequire_bearer_credentials (HTTPBearer with auto_error=False → consistent 401 + WWW-Authenticate: Bearer on missing/malformed headers) and get_authenticated_user
  • rbac.pyrequire_roles() / require_workspace_roles() dependency factories + pre-built named checks. Workspace-scoped checks validate against the workspace app registration first and only fall back to the core app registration for TREAdmin (no cross-audience elevation for other core tokens)

Route migration to the new auth layer

  • All API routes now use the auth.rbac dependencies (require_tre_admin, require_workspace_owner, etc.) instead of the legacy services/authentication.py singletons
  • The legacy module-level auth singletons and the get_current_* dependencies have been removed

Removal of AccessService abstraction

  • services/access_service.py deleted — Entra ID is the only auth provider; the abstraction was never varied
  • AzureADAuthorization is now a plain Microsoft Graph service wrapper (no OAuth2AuthorizationCodeBearer base class and no __call__); it is used only for Graph API calls (role-assignment lookups) via get_aad_service()
  • AuthConfigValidationError / UserRoleAssignmentError moved into aad_authentication.py
  • get_access_service() replaced by get_aad_service() across routes, airlock service, and repositories
  • Dead function get_app_user_roles_assignments_emails (unreachable) removed from resource_helpers.py

Tests

  • New unit tests for TokenValidator, RBAC/AuthenticatedUser helpers, the workspace-validator + TREAdmin-only fallback paths, model immutability, and missing-credential handling
  • Full api_app unit test suite passes (706 tests)
  • conftest.py auth fixtures updated for the new validator/dependency structure

Notes

  • api_app/_version.py bumped to 0.26.0
  • CHANGELOG.md updated under ENHANCEMENTS

Copilot AI review requested due to automatic review settings July 22, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors TRE API authentication by introducing a new layered api_app/auth/ package (token validation, typed user model, RBAC dependency factories) and removes the unused AccessService abstraction, while updating API routes and tests to use the new RBAC dependencies.

Changes:

  • Added api_app/auth/ with TokenValidator (JWKS via PyJWKClient), typed auth exceptions, an immutable AuthenticatedUser, and composable RBAC dependency factories.
  • Removed services/access_service.py and updated legacy AAD auth/service plumbing to use AzureADAuthorization directly for Graph calls (via get_aad_service()).
  • Migrated many route-level dependencies and tests from legacy services.authentication dependencies to auth.rbac dependencies.

Reviewed changes

Copilot reviewed 41 out of 43 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
CHANGELOG.md Documents the auth refactor and AccessService removal.
api_app/tests_ma/test_services/test_aad_access_service.py Updates imports after moving exceptions into aad_authentication.py.
api_app/tests_ma/test_db/test_repositories/test_airlock_request_repository.py Updates patches to get_aad_service in repository tests.
api_app/tests_ma/test_api/test_routes/test_workspaces.py Switches route auth overrides to new auth.rbac dependencies.
api_app/tests_ma/test_api/test_routes/test_workspace_users.py Switches route auth overrides to new auth.rbac dependencies.
api_app/tests_ma/test_api/test_routes/test_workspace_templates.py Updates admin/user dependency overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py Updates admin/user dependency overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py Updates admin/user dependency overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_shared_services.py Updates admin/user dependency overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py Updates admin/user dependency overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_requests.py Updates route auth overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_migrations.py Updates route auth overrides to auth.rbac.
api_app/tests_ma/test_api/test_routes/test_api_access.py Updates access-control tests to override auth.rbac dependencies.
api_app/tests_ma/test_api/test_routes/test_airlock.py Updates route auth overrides to auth.rbac and adjusts role param generation.
api_app/tests_ma/test_api/conftest.py Reworks global auth patching for new validator/dependency structure.
api_app/tests_ma/auth/test_token_validator.py Adds unit tests for TokenValidator.
api_app/tests_ma/auth/test_rbac.py Adds unit tests for new RBAC dependency factories and AuthenticatedUser helpers.
api_app/tests_ma/auth/init.py Adds auth tests package marker.
api_app/services/authentication.py Removes get_access_service; adds get_aad_service and updates extract_auth_information.
api_app/services/airlock.py Replaces get_access_service usage with get_aad_service.
api_app/services/access_service.py Removes unused AccessService abstraction and its exceptions.
api_app/services/aad_authentication.py Refactors JWT validation to use new auth.registry validators and keeps Graph role logic.
api_app/db/repositories/airlock_requests.py Uses get_aad_service for role assignment lookups.
api_app/auth/token_validator.py Implements TokenValidator backed by PyJWKClient.
api_app/auth/registry.py Adds cached validator registry (get_core_validator, get_workspace_validator).
api_app/auth/rbac.py Adds require_roles / require_workspace_roles factories and prebuilt role dependencies.
api_app/auth/models.py Introduces AuthenticatedUser and role enums.
api_app/auth/exceptions.py Adds typed auth exception hierarchy.
api_app/auth/dependencies.py Adds FastAPI deps for core/workspace token validation and HTTP exception mapping.
api_app/auth/init.py Adds auth package marker.
api_app/api/routes/workspaces.py Migrates router dependencies to auth.rbac; updates AAD service usage for workspace filtering.
api_app/api/routes/workspace_users.py Migrates router dependencies to auth.rbac and get_aad_service.
api_app/api/routes/workspace_templates.py Migrates admin router dependencies to auth.rbac.
api_app/api/routes/workspace_service_templates.py Migrates router dependencies to auth.rbac.
api_app/api/routes/user_resource_templates.py Migrates router dependencies to auth.rbac.
api_app/api/routes/shared_services.py Migrates router dependencies to auth.rbac.
api_app/api/routes/shared_service_templates.py Migrates router dependencies to auth.rbac.
api_app/api/routes/resource_helpers.py Switches role assignment lookup to get_aad_service and removes dead helper.
api_app/api/routes/requests.py Migrates router dependencies to auth.rbac.
api_app/api/routes/operations.py Migrates router dependencies to auth.rbac.
api_app/api/routes/migrations.py Migrates router dependencies to auth.rbac.
api_app/api/routes/costs.py Migrates router dependencies to auth.rbac.
api_app/api/routes/airlock.py Migrates router dependencies to auth.rbac.

Comment thread api_app/api/routes/workspaces.py Outdated
Comment thread api_app/services/aad_authentication.py Outdated
Comment thread api_app/tests_ma/test_api/conftest.py Outdated
Comment thread api_app/tests_ma/auth/test_rbac.py
Comment thread api_app/auth/models.py Outdated
Comment thread api_app/auth/models.py Outdated
@marrobi

marrobi commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

@copilot address PR review comments (and replies) and fix merge conflicts.

Copilot AI review requested due to automatic review settings July 22, 2026 13:46
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Unit Test Results

724 tests   724 ✅  10s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit 04b1ea0.

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 43 changed files in this pull request and generated 3 comments.

Comment thread CHANGELOG.md Outdated
Comment thread api_app/auth/registry.py
Comment thread api_app/tests_ma/test_api/conftest.py
- Remove unused get_workspace_authenticated_user (had broken Depends(lambda: None))
- Remove dead AzureADAuthorization.__call__ and helpers; make it a plain Graph service class
- Share a single PyJWKClient across token validators via registry
Copilot AI review requested due to automatic review settings July 22, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 43 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

CHANGELOG.md:12

  • Changelog entries in this file consistently include an issue/PR reference link (e.g. "(#4950)"). This new entry is missing a reference, which makes it harder to trace the change back to the discussion/PR.
* Strengthen TRE API authentication: introduce layered `auth/` package with typed exceptions, `PyJWKClient`-backed token validation with issuer checking, immutable `AuthenticatedUser` model, and composable RBAC factories; remove the `AccessService` abstraction that is no longer needed now that Entra ID is the only auth provider.

Comment thread api_app/auth/dependencies.py
- Store AuthenticatedUser.roles as an immutable tuple so roles cannot be
  escalated via in-place mutation (frozen only blocked reassignment)
- Ignore E231 in flake8 config (Python 3.12 f-string tokenisation false positives)
- Fix E306 nested-def blank line in test_migrations.py
Copilot AI review requested due to automatic review settings July 22, 2026 14:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 45 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

CHANGELOG.md:12

  • The new CHANGELOG entry does not include an issue/PR reference link, while other entries in this section consistently include one (e.g. "(#4920)"). The contribution guidelines in this repo require changelog entries to include an issue and/or PR reference.
* Strengthen TRE API authentication: introduce layered `auth/` package with typed exceptions, `PyJWKClient`-backed token validation with issuer checking, immutable `AuthenticatedUser` model, and composable RBAC factories; remove the `AccessService` abstraction that is no longer needed now that Entra ID is the only auth provider.

api_app/auth/dependencies.py:17

  • 401 responses raised from the auth dependency currently omit the WWW-Authenticate: Bearer header. The previous auth layer included this header on 401s, and omitting it can break standards-compliant clients and auth middleware that relies on it.
def _to_http_exception(exc: AuthError) -> HTTPException:
    if isinstance(exc, TokenExpired):
        return HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=strings.EXPIRED_SIGNATURE,

- Add PR reference to CHANGELOG auth entry
- Add WWW-Authenticate: Bearer header to 401 responses in auth dependencies
- Guard get_required_roles against endpoint.__defaults__ being None
Copilot AI review requested due to automatic review settings July 22, 2026 15:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

.flake8:4

  • This PR changes the global Flake8 ignore list to include E231. Disabling E231 repo-wide reduces lint signal outside the specific false-positive cases and was previously called out as something we should avoid changing globally. Prefer fixing/annotating the offending lines (e.g., targeted # noqa: E231) or pinning/upgrading the linter instead of turning the rule off for the entire codebase.
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501, W503, E231

.github/linters/.flake8:4

  • This PR adds E231 to the global ignore list for the GitHub workflow Flake8 config. This disables a general whitespace rule across the entire repo for CI, which can mask real formatting issues unrelated to the Python 3.12/pycodestyle false positives described in the comment. Consider addressing the specific violations instead of broadening the global ignore list.
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501,W503,E231

api_app/event_grid/helpers.py:46

  • publish_event ends with raise last_exc # type: ignore[misc]. This relies on a type-ignore to silence the possibility of raising None, which makes the failure path harder to reason about and can hide real bugs if the retry loop logic changes. It’s cleaner to ensure last_exc is non-None before raising.
    raise last_exc  # type: ignore[misc]

api_app/_version.py:1

  • PR description says api_app/_version.py was bumped to 0.26.0, but this change sets it to 0.26.1. Please align the implementation and PR notes/versioning intent so releases and changelog entries match the actual version being shipped.
__version__ = "0.26.1"

@marrobi

marrobi commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

/test-extended c6df049

@github-actions

Copy link
Copy Markdown

🤖 pr-bot 🤖

🏃 Running extended tests: https://github.com/microsoft/AzureTRE/actions/runs/30459549507 (with refid 28fe96b6)

(in response to this comment from @marrobi)

…Grid 503

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 29, 2026 16:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

.flake8:4

  • Ignoring E231 globally disables a useful formatting check across the entire repo and can mask real whitespace issues unrelated to the pycodestyle f-string tokenisation bug mentioned in the comment. Prefer scoping this to the specific affected files/lines via per-file-ignores or targeted # noqa: E231, rather than adding E231 to the global ignore list.
[flake8]
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501, W503, E231

.github/linters/.flake8:4

  • Same concern as the root .flake8: adding E231 to the global ignore list here broadens the workaround to the entire codebase and can hide real formatting issues. Prefer scoping the ignore to the specific affected files/lines via per-file-ignores (or local # noqa: E231) so E231 remains enforced elsewhere.
[flake8]
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501,W503,E231

api_app/auth/models.py:30

  • AuthenticatedUser.email is declared as Optional[str] = None, but downstream models require a string email (e.g. AirlockNotificationUserData.email: str in models/domain/events.py). This PR also relies on empty-string email to avoid 503s when email claims are missing; allowing None here makes it easy to reintroduce the issue when AuthenticatedUser is constructed without an email (e.g. tests_ma/test_api/conftest.py currently does). Consider making email a plain string with an empty-string default.
    id: str
    name: str
    email: Optional[str] = None
    roles: Tuple[str, ...] = Field(default_factory=tuple)

api_app/_version.py:1

  • PR description notes api_app/_version.py was bumped to 0.26.0, but this change sets it to 0.26.2. Please align the PR description and the version bump (either update the description or adjust the version) so reviewers and release tooling have a consistent reference.
__version__ = "0.26.2"

Fix airlock Event Grid 503 error by defaulting AuthenticatedUser email to an empty string. Update Nexus security to fetch admin password from Key Vault and fix UI TypeScript deprecation warning.
Copilot AI review requested due to automatic review settings July 29, 2026 16:23
@marrobi

marrobi commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

/test-extended 764f69a

@github-actions

Copy link
Copy Markdown

🤖 pr-bot 🤖

🏃 Running extended tests: https://github.com/microsoft/AzureTRE/actions/runs/30470620650 (with refid 28fe96b6)

(in response to this comment from @marrobi)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (7)

.flake8:4

  • Avoid globally ignoring flake8 E231. This suppresses a real formatting check across the whole repo (and both local + CI linter configs), and the PR thread indicates we shouldn’t change global lint settings. Prefer a targeted workaround (per-file-ignores or inline # noqa: E231) limited to the few files/lines affected by the pycodestyle/Python 3.12 false positives.
[flake8]
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501, W503, E231

.github/linters/.flake8:4

  • Avoid globally ignoring flake8 E231 in the CI linter config as well. Disabling E231 repo-wide can hide real formatting issues; use per-file-ignores or # noqa: E231 only where the false positives occur.
[flake8]
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501,W503,E231

api_app/services/airlock.py:301

  • Similarly, returning the raw Event Grid exception text to callers (EVENT_GRID_PUBLISH_FAILED.format(e)) increases information disclosure risk. If the goal is to distinguish transient failures, consider returning a stable message and logging e (or include only a safe error code) instead of echoing the exception string.
    except Exception as e:
        await airlock_request_repo.delete_item(airlock_request.id)
        logger.exception("Failed sending status_changed message")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.EVENT_GRID_PUBLISH_FAILED.format(e))

api_app/auth/models.py:33

  • AuthenticatedUser.email is typed as Optional and defaults to None, but downstream models (e.g. AirlockNotificationUserData.email) require a string; allowing None makes it easy for callers/tests to create users that later fail validation and surface as 5xx errors. Prefer making email a non-optional string with an empty-string default (the token validator already normalises missing claims to "").
    id: str
    name: str
    email: Optional[str] = None
    roles: Tuple[str, ...] = Field(default_factory=tuple)
    audience: str = ""
    is_workspace_token: bool = False

api_app/tests_ma/test_api/conftest.py:28

  • The global no_auth_token fixture builds an AuthenticatedUser without an email. Since some downstream payloads validate email as a required string, it’s safer for this default test user to match production normalisation (missing claim -> empty string) and avoid accidental None propagation in tests.
    api_app/_version.py:1
  • The PR description says api_app/_version.py was bumped to 0.26.0, but the diff sets __version__ = "0.26.2". Please align the version bump statement in the PR description / changelog with the actual version, or adjust the version here if 0.26.0 is the intended target.
__version__ = "0.26.2"

api_app/services/airlock.py:281

  • These 503 responses embed the raw exception message from Graph / Event Grid into the HTTP response body (...format(e)). This can leak internal details (endpoints, transient network info, library messages) to API clients. Consider returning a generic 503 detail (or a correlation ID) and keep the full exception details only in logs.

This issue also appears on line 298 of the same file.

        access_service = get_aad_service()
        role_assignment_details = access_service.get_workspace_user_emails_by_role_assignment(workspace)
    except Exception as e:
        logger.exception("Failed to retrieve workspace role assignments from Microsoft Graph")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.GRAPH_ROLE_ASSIGNMENT_ERROR.format(e))

Comment thread CHANGELOG.md Outdated
Copilot AI review requested due to automatic review settings July 29, 2026 16:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (7)

api_app/services/airlock.py:341

  • Same as above: returning the raw exception message from the Graph role-assignment lookup in the 503 response can leak internal details. Prefer logging the exception and returning a sanitized value.
    except Exception as e:
        logger.exception("Failed to retrieve workspace role assignments from Microsoft Graph")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.GRAPH_ROLE_ASSIGNMENT_ERROR.format(e))

api_app/services/airlock.py:350

  • Same as above: avoid including the raw Event Grid exception message in the HTTP 503 detail. Keep it in logs and return a sanitized value to the caller.
    except Exception as e:
        logger.exception("Failed sending status_changed message")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.EVENT_GRID_PUBLISH_FAILED.format(e))

.flake8:4

  • This change globally ignores E231 (whitespace after ',', ':', ';'), which reduces lint signal across the whole repo. Given prior discussion that global config shouldn’t be changed for this workaround, please scope any E231 suppression to the specific offending files/lines (e.g., # noqa: E231 or per-file-ignores) and keep the global ignore list unchanged.
ignore = E501, W503, E231

.github/linters/.flake8:4

  • This change globally ignores E231 for the CI linter configuration. As with the root .flake8, this broad suppression will hide real formatting issues repo-wide; please scope any workaround to specific files/lines instead of adding E231 to the global ignore list.
ignore = E501,W503,E231

api_app/services/airlock.py:280

  • The HTTP 503 for Graph role-assignment lookup currently includes the raw exception message (via .format(e)). Exception strings from Graph/requests can include internal details (URLs, OData queries, etc.) and are best kept in logs rather than returned to clients.

This issue also appears on line 339 of the same file.

    except Exception as e:
        logger.exception("Failed to retrieve workspace role assignments from Microsoft Graph")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.GRAPH_ROLE_ASSIGNMENT_ERROR.format(e))

api_app/services/airlock.py:301

  • The HTTP 503 returned on Event Grid publish failure currently includes the raw exception message (via .format(e)). This can leak internal endpoint/network details to callers; keep the full exception in logs and return a sanitized value instead.

This issue also appears on line 348 of the same file.

    except Exception as e:
        await airlock_request_repo.delete_item(airlock_request.id)
        logger.exception("Failed sending status_changed message")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.EVENT_GRID_PUBLISH_FAILED.format(e))

api_app/_version.py:1

  • PR description states api_app/_version.py was bumped to 0.26.0, but this change sets it to 0.26.2. Please align the PR description with the actual version bump (or adjust the version if 0.26.0 was the intended target).
__version__ = "0.26.2"

Copilot AI review requested due to automatic review settings July 29, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

.flake8:4

  • Global flake8 config now ignores E231. This suppresses whitespace-after-',' checks across the whole repo and was previously called out as something we should avoid changing globally. Prefer reverting this change and addressing any false-positives with targeted # noqa: E231 or per-file-ignores (or upgrading pycodestyle) so the rest of the codebase keeps the signal from E231.
[flake8]
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501, W503, E231

.github/linters/.flake8:4

  • The linter config under .github/linters also now ignores E231 globally, which reduces lint coverage for whitespace issues. Consider reverting this and using a more targeted suppression approach (per-file-ignores / # noqa: E231) limited to the specific false-positive cases.
[flake8]
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501,W503,E231

api_app/services/airlock.py:301

  • The 503 response on Event Grid publish failure returns the raw exception message (EVENT_GRID_PUBLISH_FAILED.format(e)). This can leak internal network/service details to clients and can also make the API surface unstable (detail changes by exception type/library version). Prefer a stable client-facing message and log the underlying exception instead (or include a correlation ID).
    except Exception as e:
        await airlock_request_repo.delete_item(airlock_request.id)
        logger.exception("Failed sending status_changed message")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.EVENT_GRID_PUBLISH_FAILED.format(e))

api_app/_version.py:1

  • PR description says api_app/_version.py was bumped to 0.26.0, but the code sets 0.26.2. Please align the PR description and version bump (either adjust the description, or update the version to the intended target) to avoid confusion during release notes / traceability.
__version__ = "0.26.2"

api_app/event_grid/helpers.py:46

  • raise last_exc # type: ignore can raise None at runtime if the retry loop ever completes without capturing an exception (e.g., future refactors change what gets caught). It also forces a type-ignore. Prefer an explicit guard so the failure mode is deterministic and type-checker friendly.
    raise last_exc  # type: ignore[misc]

api_app/services/airlock.py:281

  • The 503 response for Graph role-assignment lookup includes the raw exception string (...format(e)). Exception messages from Graph/requests can include internal URLs, request IDs, or other details that are better kept server-side. Consider returning a generic client message (and keeping full detail in logs), or gating the detailed message behind a debug setting.

This issue also appears on line 298 of the same file.

    try:
        access_service = get_aad_service()
        role_assignment_details = access_service.get_workspace_user_emails_by_role_assignment(workspace)
    except Exception as e:
        logger.exception("Failed to retrieve workspace role assignments from Microsoft Graph")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.GRAPH_ROLE_ASSIGNMENT_ERROR.format(e))

api_app/tests_ma/test_api/conftest.py:28

  • no_auth_token builds an AuthenticatedUser without an email, so email becomes None. This is inconsistent with the TokenValidator behavior (which normalizes missing email/preferred_username to an empty string) and can reintroduce the same class of failures in tests that exercise payloads expecting a string email.
    api_app/auth/models.py:33
  • AuthenticatedUser.email is typed as Optional[str] and defaults to None, but other parts of this PR (and the new TokenValidator tests) rely on email always being a string (empty string allowed) to avoid downstream validation errors. Keeping it optional makes it easy for callers/tests to accidentally reintroduce None emails.
    id: str
    name: str
    email: Optional[str] = None
    roles: Tuple[str, ...] = Field(default_factory=tuple)
    audience: str = ""
    is_workspace_token: bool = False

Copilot AI review requested due to automatic review settings July 29, 2026 19:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

.flake8:4

  • This change globally disables E231 (whitespace after ','), which can hide real formatting issues across the repo. Since this was introduced as a workaround for a pycodestyle edge case, it should be scoped (per-file-ignores / targeted noqa) rather than disabling the rule repo-wide.
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501, W503, E231

.github/linters/.flake8:4

  • Adding E231 to the global ignore list weakens formatting checks for all Python code. If specific files are triggering false positives, prefer scoping the ignore to those files rather than disabling E231 for the entire repo.
# E231 is ignored because pycodestyle on Python 3.12+ mis-tokenises f-string
# contents (commas/colons inside URL, OData and JSON string literals) as code.
ignore = E501,W503,E231

api_app/tests_ma/test_api/conftest.py:28

  • The global no_auth_token fixture constructs an AuthenticatedUser without an email, so email defaults to None. Some code paths (e.g. Event Grid airlock notification payload validation) require email to be a string, and this fixture can accidentally reintroduce the previous None-email regression in tests that reuse it.

@marrobi

marrobi commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

/test-force-approve 04b1ea0

@github-actions

Copy link
Copy Markdown

🤖 pr-bot 🤖

✅ Marking tests as complete (for commit 04b1ea0)

(in response to this comment from @marrobi)

@marrobi
marrobi merged commit 9a1691e into microsoft:main Jul 29, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants